/*
 * @Author: 肖思汗 
 * @Date: 2019-07-03 22:22:59 
 * @Last Modified by: 肖思汗
 * @Last Modified time: 2020-02-13 00:51:41
 */

/**
 * @export 把一个普通json对象变成过渡动画对象
 * @param {*} json
 */

// 贝塞尔参数验证
export const bezier = {
    message: '请输入正确格式的贝塞尔参数如: [0.8, 0, 0.2, 1]',
    pattern: /^\[((0|1|0\.\d+),){3}(0|1|0\.\d+)\]$/
};

let i = 0;  // 计数器

export default class transition {

    constructor(param = {}) {

        for (let index in param) {

            // 每一个缓动参数都有一个起始值
            Object.defineProperty(this, `${index}_start`, {
                value: parseFloat(param[index]),
                configurable: false, // 此属性能否删除和修改
                enumerable: false, // 此属性能否被遍历
                writable: true // 此属性的值能否修改
            });

            // 每一个缓动参数都有一个结束值
            Object.defineProperty(this, `${index}_end`, {
                value: parseFloat(param[index]),
                configurable: false, // 此属性能否删除和修改
                enumerable: false, // 此属性能否被遍历
                writable: true // 此属性的值能否修改
            });

            // 每一个缓动参数都有一个独立的起始时间
            Object.defineProperty(this, `${index}_now`, {
                value: performance.now(),
                configurable: false, // 此属性能否删除和修改
                enumerable: false, // 此属性能否被遍历
                writable: true // 此属性的值能否修改
            });

            // 每一个缓动参数都有一个独立的时间进度
            Object.defineProperty(this, `${index}_timeProgress`, {
                configurable: false, // 此属性能否删除和修改
                enumerable: false, // 此属性能否被遍历
                get() {
                    return Math.min(1, (performance.now() - this[`${index}_now`]) / (this.duration || 500));
                }
            });

            Object.defineProperty(this, index, {
                configurable: false, // 此属性能否删除和修改
                enumerable: true, // 此属性能否被遍历
                get() {
                    // 返回值时是否需要计算
                    if (performance.now() - this[`${index}_now`] < this.duration) {
                        return this.computerByBezier(index);
                    } 
                    return this[`${index}_end`];
                    
                },
                set(value = 0) {

                    // 如果值是否改变 起始位置都设置为当前位置
                    this[`${index}_start`] = this.computerByBezier(index);
                    // 当值改变时 如果0毫秒前没有其他属性赋值  就缓存进度 并将进度设置为0
                    // 当值改变时 如果0毫秒前有其他属性赋值 就不用管   上面的 computerByBezier 回自动使用缓存进度来计算 start 位置
                    if (this[`${index}_end`] !== value) {
                        this[`${index}_now`] = performance.now();
                    }

                    this[`${index}_end`] = value;

                }
            });

        }

        // 设置贝塞尔曲线
        Object.defineProperty(this, 'bezier', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            get() {
                return this._bezier;
            },
            set(value = [0.8, 0, 0.2, 1]) {
                // 贝塞尔参数验证
                if (bezier.pattern.test(JSON.stringify(value))) {
                    this._bezier = value;
                    this.xBezier = [0, value[0], value[2], 1];
                    this.yBezier = [0, value[1], value[3], 1];
                } else {
                    throw bezier.message;
                }
            }
        });

        // 获取当前缓动效果的时间进度
        Object.defineProperty(this, 'timeProgress', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            get() {

                for (i in this) {

                    if (this.hasOwnProperty(`${i}_timeProgress`) === true) {
                        if (this[`${i}_timeProgress`] !== 1) {
                            return 0;
                        }
                    }

                }

                return 1;
            }
        });

        // 值被设置时的时间戳
        Object.defineProperty(this, 'now', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            writable: true, // 此属性的值能否修改
            value: -Infinity // 起始时间为负无穷大  可以保证刚刚初始化是  timeProgress 为 1
        });

        // 当前的的贝塞尔曲线
        Object.defineProperty(this, '_bezier', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            writable: true, // 此属性的值能否修改
            value: [0.8, 0, 0.2, 1]
        });

        // 其实位置2个控制杆的坐标
        Object.defineProperty(this, 'xBezier', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            writable: true, // 此属性的值能否修改
            value: [0, 0.8, 0.2, 1]
        });

        // 终止位置2个控制杆的坐标
        Object.defineProperty(this, 'yBezier', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            writable: true, // 此属性的值能否修改
            value: [0, 0, 1, 1]
        });

        // 终止位置2个控制杆的坐标
        Object.defineProperty(this, 'duration', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            writable: true, // 此属性的值能否修改
            value: 500
        });

        // 推导公式 通过贝塞尔求 和 t 求坐标
        Object.defineProperty(this, 'getVByBezier', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            writable: false, // 此属性的值能否修改
            value: (() => {

                var bezier = null;

                return (array, t) => {
                    bezier = array.map(item => item);
                    while (bezier.length > 1) {
                        bezier.map((item, key) => {
                            if (bezier[key + 1]) {
                                bezier[key] += (bezier[key + 1] - bezier[key]) * t;
                            }
                        });
                        bezier.pop();
                    }
                    return bezier[0];
                };

            })()
        });

        // 推导公式 通过贝塞尔求 和 t 求坐标
        Object.defineProperty(this, 'computerByBezier', {
            configurable: false, // 此属性能否删除和修改
            enumerable: false, // 此属性能否被遍历
            writable: false, // 此属性的值能否修改
            value: (() => {
                // 斜率
                let t = 0;

                // 最近0毫秒前有人赋值过时, 就顺便缓存了进度
                // 就用缓存的进度来计算
                let tp = 0;

                return key => {
                    t = 0;
                    tp = this[`${key}_timeProgress`];

                    // 无限接近法 求斜率t
                    while (this.getVByBezier(this.xBezier, t + 0.1) < tp) { t += 0.1; }
                    while (this.getVByBezier(this.xBezier, t + 0.01) < tp) { t += 0.01; }

                    t = Math.min(1, t); // 斜率最大为1

                    // 通过斜率求y值
                    return this.getVByBezier(this.yBezier, t) * (this[`${key}_end`] - this[`${key}_start`]) + this[`${key}_start`];
                };
            })()
        });

    } // constructor

}

